﻿using Devil;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace GameToolkit.Editor
{
    [CustomEditor(typeof(SharingConfigBase), true, isFallback = true), CanEditMultipleObjects]
    public class SharingConfigMonoEditor : UnityEditor.Editor
    {
        SerializedProperty m_Configuration;
        SerializedObject m_ConfigInst;

        [SerializeField]
        string m_WorkFolder;

        protected virtual void OnEnable()
        {
            m_Configuration = serializedObject.FindProperty("m_Configuration");
            var path = AssetDatabase.GetAssetPath(target);
            if (!string.IsNullOrEmpty(path))
                m_WorkFolder = NativeFileSystem.GetFolderName(path);
            else if(string.IsNullOrEmpty(m_WorkFolder))
                m_WorkFolder = "Assets";
        }

        protected virtual void OnDisable()
        {
            if(m_ConfigInst != null)
            {
                m_ConfigInst.Dispose();
                m_ConfigInst = null;
            }
        }

        void CreateConfig(System.Type type)
        {
            if (m_Configuration == null || serializedObject == null)
                return;
            var file = EditorUtility.SaveFilePanelInProject($"Create Config: {type.Name}", $"{type.Name}", "asset", $"Save Config file: {type.Name}");
            if (string.IsNullOrEmpty(file))
                return;
            var cfg = ScriptableObject.CreateInstance(type);
            if (cfg == null)
                return;
            m_Configuration.objectReferenceValue = cfg;
            serializedObject.ApplyModifiedProperties();
            if (m_ConfigInst != null)
                m_ConfigInst.Dispose();
            m_ConfigInst = new SerializedObject(cfg);
            if(NativeFileSystem.HasFile(file))
            {
                cfg.name = GUID.Generate().ToString();
                AssetDatabase.AddObjectToAsset(cfg, file);
            }
            else
            {
                cfg.name = NativeFileSystem.GetFileName(file, true);
                AssetDatabase.CreateAsset(cfg, file);
            }
            AssetDatabase.ImportAsset(file);
        }


        public override void OnInspectorGUI()
        {
            if (m_Configuration == null)
            {
                var type = target.GetType().Name;
                EditorGUILayout.HelpBox($"The type '{this.target.GetType().Name}' is not support (the type must be inherit from SharingConfigMono<T>).", MessageType.Error);
                return;
            }
            serializedObject.Update();
            EditorGUI.BeginChangeCheck();
            var iter = m_Configuration.Copy();
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.PropertyField(iter);
            EditorGUI.BeginDisabledGroup(iter.hasMultipleDifferentValues || iter.objectReferenceValue != null);
            var newInst = GUILayout.Button("+", GUILayout.Width(20));
            EditorGUI.EndDisabledGroup();
            EditorGUILayout.EndHorizontal();
            var change = EditorGUI.EndChangeCheck();
            if (newInst)
            {
                var flags = BindingFlags.NonPublic | BindingFlags.Instance;
                FieldInfo field = null;
                var tp = target.GetType().BaseType;
                while (field == null && tp != null)
                {
                    field = tp.GetField("m_Configuration", flags);
                    tp = tp.BaseType;
                }
                Debug.Assert(field != null, "don't find field: m_Configuration");
                var types = new List<System.Type>();
                Ref.GetAssemblyTypes(types, (tp) => !tp.IsAbstract && Ref.IsTypeInheritedFrom(tp, field.FieldType));
                Debug.Assert(types.Count > 0, $"no available type for {target.GetType().Name}'s configuration.");
                if(types.Count > 1)
                {
                    var menu = new GenericMenu();
                    GenericMenu.MenuFunction2 selItem = (addType) =>
                    {
                        CreateConfig(addType as System.Type);
                    };
                    for (int i = 0; i < types.Count; i++)
                    {
                        var tname = types[i].FullName.Replace('.', '/');
                        menu.AddItem(new GUIContent(tname), false, selItem, types[i]);
                    }
                    menu.ShowAsContext();
                }
                else if(types.Count == 1)
                {
                    CreateConfig(types[0]);
                }
            }

            var inst = iter.objectReferenceValue;
            var current = m_ConfigInst == null ? null : m_ConfigInst.targetObject;
            if (!m_Configuration.hasMultipleDifferentValues && inst != current)
            {
                if (m_ConfigInst != null)
                    m_ConfigInst.Dispose();
                m_ConfigInst = inst == null ? null : new SerializedObject(inst);
            }
            if (m_ConfigInst != null)
            {
                EditorGUI.indentLevel++;
                m_ConfigInst.Update();
                EditorGUI.BeginChangeCheck();
                var instIter = m_ConfigInst.GetIterator();
                instIter.Next(true);
                // skip script
                instIter.NextVisible(false);
                while (instIter.NextVisible(false))
                {
                    EditorGUILayout.PropertyField(instIter, true);
                }
                var dirtyCfg = EditorGUI.EndChangeCheck();
                EditorGUI.indentLevel--;
                if (dirtyCfg)
                    m_ConfigInst.ApplyModifiedProperties();
            }

            EditorGUI.BeginChangeCheck();
            while (iter.NextVisible(false))
            {
                EditorGUILayout.PropertyField(iter, true);
            }
            change |= EditorGUI.EndChangeCheck();
            if (change)
                serializedObject.ApplyModifiedProperties();
        }
    }
}
